package bbqtorrent;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import sun.security.provider.MD5;
import tools.Format;

public class Torrent {
		private String createdBy;
		private String announce;
		private int creationDate;
		private String comment;
		private String encoding;
		private ArrayList<String> announceList;
		
		private int pieceLength;
		private byte[] pieces;
		private ArrayList<String> piecesString;
		private int prive;
		private String name;
		
		//SINGLE-FILE MODE		
		private long length;
		private MD5 md5sum;
		
		//MULTIPLE-FILE MODE
		private boolean multi;
		private ArrayList<Map<String, Long>> files;		
		private byte[] hashInfo;
		
		

		public Torrent() {
			this.createdBy = null;
			this.announce = null;
			this.creationDate = -1;
			this.comment = null;
			this.encoding = null;
			this.announceList = new ArrayList<String>();
			this.pieceLength = -1;
			this.pieces = null;
			this.prive = -1;
			this.name = null;
			this.length = -1;
			this.md5sum = null;
			this.multi = false;
			this.files = new ArrayList<Map<String, Long>>();
			this.piecesString = new ArrayList<String>();
                        this.hashInfo = new byte[20];
		}
		
		private void fill(String key, BeValue value) throws InvalidBEncodingException {
			if(key.equals("created by")) {
				this.createdBy = value.getString();
			} else if(key.equals("announce")) {
				this.announce = value.getString();
			} else if(key.equals("encoding")) {
				this.encoding = value.getString();
			} else if(key.equals("announce-list")) {
				List list = value.getList();				
				for(int i=0; i<list.size(); i++) {
					List curList = ((BeValue) list.get(i)).getList();					
					for(int j=0; j<curList.size(); j++) {
						this.announceList.add(((BeValue) curList.get(j)).getString());
					}
				}
			} else if(key.equals("creation date")) {
				this.creationDate = value.getInt();
			} else if(key.equals("comment")) {
				this.comment = value.getString();
			} else if(key.equals("info")) {
				this.load(value);
			} else if(key.equals("pieces")) {
				this.pieces = value.getBytes();
			} else if(key.equals("name")) {
				this.name = value.getString();
			} else if(key.equals("length")) {
				this.length = value.getLong();
			} else if(key.equals("piece length")) {
				this.pieceLength = value.getInt();
			} else if(key.equals("private")) {
				this.prive = value.getInt();
			} else if(key.equals("files")){
				this.multi = true;
				List list = value.getList();
				
				for(int i=0; i<list.size(); i++) {					
					Map map = ((BeValue) list.get(i)).getMap();
					
					Set keys = map.keySet();
					Collection values = map.values();
					Iterator<String> iterKey = keys.iterator();	
					Iterator<BeValue> iterValue = values.iterator();
					String keyMap = "";
					BeValue valueMap = null;
					
					String filePath = "";
					long fileSize = -1;
					MD5 fileMd5sum = null;
					
					while(iterKey.hasNext() && iterValue.hasNext()) {
						keyMap = iterKey.next();
						valueMap = iterValue.next();
						if(keyMap.equals("length")) {
							fileSize = valueMap.getLong();	
							this.length += fileSize;
						} else if(keyMap.equals("path")) {
							List pathList = valueMap.getList();
							filePath = "";
							if(pathList.size() > 1) {								
								for(int j=0; j<pathList.size(); j++) {
									filePath += "/" + ((BeValue) pathList.get(j)).getString();
								}
							} else {
								filePath = ((BeValue) pathList.get(0)).getString();
							}
							
						} else if(keyMap.equals("md5sum")) {														
							fileMd5sum = null;
						}
					}
					
					Map<String, Long> fileMap = new HashMap<String, Long>();
					fileMap.put(filePath, fileSize);					
					this.files.add(fileMap);
				}
			}
					
		}

    public ArrayList<String> getPiecesString() {
        return piecesString;
    }
		


		public void load(BeValue beValue) throws InvalidBEncodingException {
			Map map = beValue.getMap();
						
			Set keys = map.keySet();
			Collection values = map.values();
			Iterator<String> iterKey = keys.iterator();	
			Iterator<BeValue> iterValue = values.iterator();
			String key = "";
			BeValue value = null;
			
			while(iterKey.hasNext() && iterValue.hasNext()) {
				key = iterKey.next();
				value = iterValue.next();				
				this.fill(key, value);
			}
			
			
			if(this.pieces != null) {
				byte[] hash = new byte[20];
				int j = 0;
				
				for(int i=0; i<this.pieces.length; i++) {				
					if(i%20==0) {
						hash[j] = this.pieces[i];
						this.piecesString.add(Format.byteArray2Hex(hash));
						hash = new byte[20];
						j = 0;
					} else {
						hash[j] = this.pieces[i];
						j++;
					}
				}
			}
		}
		
		private String format(String key, Object value) {
			if(value == null)
				return "";
			
			if(value instanceof List && ((List) value).size() == 0)
				return "";
			
			if(value instanceof Integer && ((Integer) value) == -1)
				return "";
			
			return key+" : "+value+"\n";
		}

		@Override
		public String toString() {
			String toString = 
			format("Created By", this.createdBy) +
			format("Announce", this.announce) +
			format("Creation Date", this.creationDate) +
			format("Comment", this.comment) +
			format("Encoding", this.encoding) +
			format("Announce-List", this.announceList) +
			format("Piece length", this.pieceLength)+
			/*format("Pieces length % 20", this.pieces.length % 20) +*/
			format("Private", this.prive) +
			format("Name", this.name)+
			format("Length", this.length)+
			format("Md5sum", this.md5sum)+
			format("Multiple-File", this.multi)+
			format("Files", this.files);	
			
			return toString;
		}		
		
		
		public boolean isMulti() {
			return multi;
		}

		public ArrayList<Map<String, Long>> getFiles() {
			return files;
		}

		public String getCreatedBy() {
			return createdBy;
		}

		public String getAnnounce() {
			return announce;
		}

		public int getCreationDate() {
			return creationDate;
		}

		public String getComment() {
			return comment;
		}

		public String getEncoding() {
			return encoding;
		}

		public ArrayList<String> getAnnounceList() {
			return announceList;
		}

		public int getPieceLength() {
			return pieceLength;
		}

		public byte[] getPieces() {
			return pieces;
		}

		public int getPrive() {
			return prive;
		}

		public String getName() {
			return name;
		}

		public long getLength() {
			return length;
		}

		public MD5 getMd5sum() {
			return md5sum;
		}

		public void setHashInfo(byte[] hashInfo) {

			this.hashInfo = hashInfo;			
		}

		public byte[] getHashInfo() {
			return hashInfo;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + Arrays.hashCode(hashInfo);
			result = prime * result + ((name == null) ? 0 : name.hashCode());
			result = prime * result + prive;
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			Torrent other = (Torrent) obj;
			if (!Arrays.equals(hashInfo, other.hashInfo))
				return false;
			if (name == null) {
				if (other.name != null)
					return false;
			} else if (!name.equals(other.name))
				return false;
			if (prive != other.prive)
				return false;
			return true;
		}
		
		
		
}
